﻿using Microsoft.Samples.Debugging.CorDebug;
using Microsoft.Samples.Debugging.MdbgEngine;
using Microsoft.Samples.Tools.Mdbg;
using System;
using System.Diagnostics;
using System.Linq;

namespace LowLevelDesign.MDbg 
{
    public sealed class InjectionException : Exception
    {
        public InjectionException(String message) : base(message) { }
    }

    [MDbgExtensionEntryPointClass(Url = "http://lowleveldesign.wordpress.com")]
    internal sealed class InjectExtension : CommandBase
    {
        /// <summary>
        /// Loads extension into the debugger.
        /// </summary>
        public static void LoadExtension()
        {
            MDbgAttributeDefinedCommand.AddCommandsFromType(Shell.Commands, typeof(InjectExtension));
            WriteOutput("inject extension loaded");
        }

        private static void InjectAssemblyInternal(CorThread thread, CorAppDomain appdomain, String assembly)
        {
            CorEval eval = thread.CreateEval();
            // if we do not create a local copy we will get write memory problem
            String asm = String.Copy(assembly);
            eval.NewString(asm);
            Debugger.Processes.Active.Go().WaitOne();
            if (!(Debugger.Processes.Active.StopReason is EvalCompleteStopReason))
            {
                throw new InjectionException("ERROR: injection was unsuccesful: assembly name evaluation failed.");
            }
            var assemblyName = (Debugger.Processes.Active.StopReason as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

            MDbgFunction func = Debugger.Processes.Active.ResolveFunctionNameFromScope(
                        "System.AppDomain.get_CurrentDomain", appdomain);
            eval.CallFunction(func.CorFunction, null);
            Debugger.Processes.Active.Go().WaitOne();
            if (!(Debugger.Processes.Active.StopReason is EvalCompleteStopReason))
            {
                throw new MDbgShellException("Injection was unsuccesful: get_CurrentDomain failed");
            }
            var currentDomain = (Debugger.Processes.Active.StopReason as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

            // call execute assembly
            func = Debugger.Processes.Active.ResolveFunctionNameFromScope("System.AppDomain.ExecuteAssembly", appdomain);
            eval.CallFunction(func.CorFunction, new[] { currentDomain, assemblyName });
            Debugger.Processes.Active.Go().WaitOne();

            // now display result of the funceval
            if (!(Debugger.Processes.Active.StopReason is EvalCompleteStopReason))
            {
                throw new MDbgShellException("Injection was unsuccessful: ExecuteAssembly failed");
            }
        }

        [CommandDescription(CommandName = "inject",
                            MinimumAbbrev = 2,
                            ShortHelp = "Inject command.",
                            LongHelp = @"Runs code from a given assembly in the debuggee.
Usage:
    inject <assembly-path> [-ad <appdomain-name>]

-ad <appdomain-name>  inject code into a specific appdomain")]
        public static void InjectAssembly(String argString)
        {
            var parser = new ArgParser(argString, "ad:1");
            if (parser.Count != 1)
            {
                throw new MDbgShellException("Assembly is required");
            }
            var assembly = parser.GetArgument(0).AsString;
            String appdomain = null;
            if (parser.OptionPassed("ad"))
            {
                appdomain = parser.GetOption("ad").AsString;
            }

            var thread = Debugger.Processes.Active.Threads.Active;
            CorAppDomain corappdomain = null;
            if (appdomain != null)
            {
                corappdomain = Debugger.Processes.Active.AppDomains.Cast<MDbgAppDomain>().Where(
                                     ad => String.Equals(ad.CorAppDomain.Name, appdomain, StringComparison.OrdinalIgnoreCase)).Single().CorAppDomain;
            }
            InjectAssemblyInternal(thread.CorThread, corappdomain, assembly);
        }
    }
}
